Micron Document
🎖️GitЯра🎖️


Displaying Raw • Download

feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/kml/KmlToGeoJson.kt renovate/fastlane-2.x-lockfile (056eed64) Text, 5.35 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.map.kml

Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787nl.adaptivity.xmlutil.EventType
Tff7b72import T7ee787nl.adaptivity.xmlutil.xmlStreaming
Tff7b72import T7ee787org.meshtastic.core.common.util.safeCatching

T8b949e/**
* Converts KML into the GeoJSON MapLibre can read.
*
* MapLibre has no KML source, and the F-Droid map used to drop `LayerType.KML` imports on the floor: the file appeared
* in the layers sheet, could be toggled, and drew nothing.
*
* Hand-written rather than a new dependency. The KML that reaches this app is what other mapping tools export —
* Placemarks with a Point, LineString or Polygon and a colour — and the subset the renderers draw is no wider than what
* this reads. It parses through xmlutil, which the app already resolves for CoT XML, so this is common code and runs
* wherever the app does rather than only on Android.
*
* Styles come out as [simplestyle-spec](https://github.com/mapbox/simplestyle-spec) properties, which is what both
* flavors' renderers already read, so a converted KML is styled by the same code path as an imported GeoJSON.
*
* Unsupported by design, because the renderers on both sides ignore them anyway: NetworkLink, ScreenOverlay, Model, and
* time spans. GroundOverlays are parsed — they come back from [convertDocument] beside the GeoJSON, because an image
* draped over a box has no GeoJSON representation at all.
*/
Tff7b72object T56d364KmlToGeoJson Tb4b4b4{

T8b949e/**
* Reads [kml] and returns a GeoJSON `FeatureCollection` document, or null if nothing mappable was found.
*
* Takes the document as text rather than a stream: a KMZ is a zip, and unpacking one is the host's job — this
* converter only ever sees the KML inside.
*/
Tff7b72fun Td2a8ffconvertTb4b4b4(Te6edf3kmlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String? Tff7b72= Te6edf3convertDocumentTb4b4b4(Te6edf3kmlTb4b4b4)Tb4b4b4.Te6edf3geoJson

T8b949e/**
* Everything drawable in [kml]: the vector features as a GeoJSON `FeatureCollection` (null if there are none), plus
* any `<GroundOverlay>` images. A document can hold either without the other — a pure overlay export has no
* placemarks at all, and it must not read as "nothing mappable".
*/
Tff7b72fun Td2a8ffconvertDocumentTb4b4b4(Te6edf3kmlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3KmlConversion Tb4b4b4{
Tff7b72val Tb4b4b4(Te6edf3featuresTb4b4b4, Te6edf3overlaysTb4b4b4) Tff7b72=
Te6edf3safeCatching Tb4b4b4{ Te6edf3parseDocumentTb4b4b4(Te6edf3kmlTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3failure Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3wTb4b4b4(Te6edf3failureTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffCould not read an imported KMLTa5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3getOrDefaultTb4b4b4(Te6edf3emptyListTff7b72<Tffa657StringTff7b72>Tb4b4b4(Tb4b4b4) Te6edf3to Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)

T8b949e// An empty collection would draw nothing while claiming the import had succeeded.
Tff7b72val Te6edf3geoJson Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3featuresTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72null
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3featuresTb4b4b4.Te6edf3joinToStringTb4b4b4(
Te6edf3separator Tff7b72= Ta5d6ff"Ta5d6ff,Ta5d6ff"Tb4b4b4,
Te6edf3prefix Tff7b72= Ta5d6ff"Ta5d6ff{Ta5d6ff\"Ta5d6fftypeTa5d6ff\"Ta5d6ff:Ta5d6ff\"Ta5d6ffFeatureCollectionTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fffeaturesTa5d6ff\"Ta5d6ff:[Ta5d6ff"Tb4b4b4,
Te6edf3postfix Tff7b72= Ta5d6ff"Ta5d6ff]}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3KmlConversionTb4b4b4(Te6edf3geoJsonTb4b4b4, Te6edf3overlaysTb4b4b4)
Tb4b4b4}

Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4) T8b949e// one branch per top-level KML element; splitting hides the shape
Tff7b72private Tff7b72fun Td2a8ffparseDocumentTb4b4b4(Te6edf3kmlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3PairTff7b72<Te6edf3ListTff7b72<Tffa657StringTff7b72>Tb4b4b4, Te6edf3ListTff7b72<Te6edf3KmlGroundOverlayTff7b72>Tff7b72> Tb4b4b4{
Tff7b72val Te6edf3reader Tff7b72= Te6edf3xmlStreamingTb4b4b4.Te6edf3newReaderTb4b4b4(Te6edf3kmlTb4b4b4)

Tff7b72val Te6edf3styles Tff7b72= Te6edf3mutableMapOfTff7b72<Tffa657StringTb4b4b4, Te6edf3KmlStyleTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3styleMaps Tff7b72= Te6edf3mutableMapOfTff7b72<Tffa657StringTb4b4b4, Tffa657StringTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3placemarks Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3PlacemarkTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3overlays Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3KmlGroundOverlayTff7b72>Tb4b4b4(Tb4b4b4)

Tff7b72var Te6edf3done Tff7b72= Tff7b72false
Tff7b72while Tb4b4b4(Tff7b72!Te6edf3doneTb4b4b4) Tb4b4b4{
Tff7b72when Tb4b4b4(Te6edf3readerTb4b4b4.Te6edf3nextTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3EventTypeTb4b4b4.Te6edf3START_ELEMENT Tff7b72-Tff7b72>
Tff7b72when Tb4b4b4(Te6edf3readerTb4b4b4.Te6edf3localNameTb4b4b4) Tb4b4b4{
Ta5d6ff"Ta5d6ffStyleTa5d6ff" Tff7b72-Tff7b72> Te6edf3readerTb4b4b4.Te6edf3readStyleTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Tb4b4b4(Te6edf3idTb4b4b4, Te6edf3styleTb4b4b4) Tff7b72-Tff7b72> Te6edf3stylesTff7b72[Ta5d6ff"Ta5d6ff#Tffd700$Te6edf3idTa5d6ff"Tff7b72] Tff7b72= Te6edf3style Tb4b4b4}
Ta5d6ff"Ta5d6ffStyleMapTa5d6ff" Tff7b72-Tff7b72> Te6edf3readerTb4b4b4.Te6edf3readStyleMapTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Tb4b4b4(Te6edf3idTb4b4b4, Te6edf3targetTb4b4b4) Tff7b72-Tff7b72> Te6edf3styleMapsTff7b72[Ta5d6ff"Ta5d6ff#Tffd700$Te6edf3idTa5d6ff"Tff7b72] Tff7b72= Te6edf3target Tb4b4b4}
Ta5d6ff"Ta5d6ffPlacemarkTa5d6ff" Tff7b72-Tff7b72> Te6edf3placemarks Tff7b72+Tff7b72= Te6edf3readerTb4b4b4.Te6edf3readPlacemarkTb4b4b4(Tb4b4b4)
Ta5d6ff"Ta5d6ffGroundOverlayTa5d6ff" Tff7b72-Tff7b72> Te6edf3readerTb4b4b4.Te6edf3readGroundOverlayTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3overlays Tff7b72+Tff7b72= Tffa657it Tb4b4b4}
Tff7b72else Tff7b72-Tff7b72> Tffa657Unit
Tb4b4b4}

Te6edf3EventTypeTb4b4b4.Te6edf3END_DOCUMENT Tff7b72-Tff7b72> Te6edf3done Tff7b72= Tff7b72true

Tff7b72else Tff7b72-Tff7b72> Tffa657Unit
Tb4b4b4}
Tb4b4b4}

Tff7b72val Te6edf3features Tff7b72=
Te6edf3placemarksTb4b4b4.Te6edf3flatMap Tb4b4b4{ Te6edf3placemark Tff7b72-Tff7b72>
T8b949e// An inline Style outranks a styleUrl, per the KML reference. A StyleMap points at the styles for
T8b949e// normal and highlighted; only the normal one is ever drawn here.
Tff7b72val Te6edf3resolved Tff7b72= Te6edf3placemarkTb4b4b4.Te6edf3inlineStyle Tff7b72?: Te6edf3placemarkTb4b4b4.Te6edf3styleUrlTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3stylesTff7b72[Te6edf3styleMapsTff7b72[Tffa657itTff7b72] Tff7b72?: Tffa657itTff7b72] Tb4b4b4}
Te6edf3placemarkTb4b4b4.Te6edf3geometriesTb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3geometry Tff7b72-Tff7b72> Te6edf3geometryTb4b4b4.Te6edf3toFeatureTb4b4b4(Te6edf3placemarkTb4b4b4, Te6edf3resolvedTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3features Te6edf3to Te6edf3overlays
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72const Tff7b72val Te6edf3TAG Tff7b72= Ta5d6ff"Ta5d6ffKmlToGeoJsonTa5d6ff"

Served by rngit 1.5.2 - Generated in 0.04s